package deroulementjeu;


import java.awt.Color;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;

import terrain.CaseTerrain;
import terrain.Itineraire;
import unite.Distance;
import unite.Dresseur;
import unite.Jouable;
import unite.Pokemon;
import unite.Potion;
import unite.TypeStrategie;
import unite.TypeUnite;
import unite.Unite;


public class Equipe implements Serializable {
    private static final long serialVersionUID = 1L;

    /** Nom de l'équipe */
    private String nom;
	
    /** Couleur de l'équipe */
    private Color couleur;
	
    /** Type de l'équipe */
    private TypeEquipe type;
	
    /** Nombre de potions possédées par l'équipe */
    private int nbPotions;
	
    /** Ensemble des unités de l'équipe */
    private ArrayList<Unite> unites;
	
    /** Partie à laquelle participe l'équipe (permet de faire le lien avec les règles */
    private Partie partie;
	
    /** Unité en cours d'utilisation */
    private Unite uniteCourante;
	
    /** Equipe ennemie */
    private Equipe ennemi;
	
    /**
     * Constructeur de la classe Equipe
     * @param nom nom de l'équipe
     * @param couleur couleur de l'équipe
     * @param type type de joueur
     */
    public Equipe(Partie p, String nom, Color couleur, TypeEquipe type) {
        this.partie = p;
        this.setNom(nom);
        this.setCouleur(couleur);
        this.setType(type);
        // Nombre de potions initial (si l'équipe est la "troisième équipe" : pas de potions)
        if (type != TypeEquipe.OBJET) {
            setNbPotions(partie.getRegle().getNbPotion());
        } else {
            setNbPotions(0);
        }
        unites = new ArrayList<Unite>();
        ennemi = null;
    }
	
    /**
     * Fixer l'ennemi de l'équipe
     * @param e l'ennemi de l'équipe
     */
    public void setEnnemi(Equipe e) {
        ennemi = e;
    }
	
    /**
     * Retourne l'ennemi de l'équipe
     * @return l'equipe ennemie
     */
    public Equipe getEnnemi() {
        return ennemi;
    }
	
    /**
     * Récupération du dresseur de l'équipe (ou du premier, dans le cas où il y en aurait plusieurs)
     * @return le dresseur de l'équipe (ou null s'il n'y en a pas)
     */
    public Dresseur getDresseur() {
        int i = 0;

        while (i < unites.size()) {
            if (unites.get(i).getType() == TypeUnite.DRESSEUR) {
                return (Dresseur) unites.get(i);
            } else {
                i++;
            }
        }
        return null;
    }
	
    /**
     * Tour de jeu d'IA
     * Pour toutes les unités :
     *   - si pokémon ennemi dans leur champ d'action -> attaquer
     *   - si potion dans leur champ d'action -> récupérer (= attaquer la potion)
     *   - sinon :
     *   	- si offensive -> aller vers le QG de l'autre
     *   	- si défensive -> aller vers son propre QG
     */
    public void tourDeJeuIA() {
        for (int i = 0; i < unites.size(); i++) {
            Unite u = unites.get(i);
            // Déterminer aléatoirement si l'unité joue à ce tour
            // Plus le niveau de l'IA est élevé, plus elle a de chance de jouer
            boolean jouer;

            if (u.getRegles().getNivIA() == 3) {
                jouer = true;
            } else {
                int random = (int) (Math.random() * 10);

                if (u.getRegles().getNivIA() == 1) {
                    if (random <= 3) {
                        jouer = true;
                    } else {
                        jouer = false;
                    }
                } else if (random <= 6) {
                    jouer = true;
                } else {
                    jouer = false;
                }
            }
			
            if (jouer) {
                // Si le pokémon est très faible : utiliser une potion
                if (u.getPv() < 5 && this.nbPotions > 0) {
                    Jouable j = (Jouable) u;

                    j.utiliserPotion();
                    j.setDeplacement(0);
                }
				
                if (u.getType() != TypeUnite.DRESSEUR) {
                    ArrayList<CaseTerrain> champAction = new ArrayList<CaseTerrain>();
                    Itineraire it = new Itineraire((Pokemon) u);
                    CaseTerrain objectif = null;

                    if (u.getStrategie() == TypeStrategie.OFFENSIVE) {
                        // Unité offensive :
                        // cherche à s'approcher du dresseur ennemi
                        objectif = ennemi.getDresseur().getCaseTerrain();
                    } else {
                        // Unité défensive :
                        // cherche à s'approcher de son dresseur
                        objectif = this.getDresseur().getCaseTerrain();	
                    }
					
                    // Calcul de l'itinéraire vers l'objectif de l'unité
                    // et du champ d'action de l'unité en même temps
                    objectif.coutDAcces(u, it, champAction);
					
                    // Si l'unité est à une case adjacente de 
                    // son but. 
                    // Offensive -> attaque le QG
                    // Défensive -> reste sur place (ne fait rien)
                    if (objectif.getCaseAdjacente().contains(u.getCaseTerrain())) {
                        
                        // Attaquer le QG ennemi = créer l'itinéraire vers le QG ennemi
                        // et laisser faire le reste
                        if (u.getStrategie() == TypeStrategie.OFFENSIVE) {
                            it.valider();
                        }
                    }
                    // Si l'unité peut attaquer à distance :
                    // Essayer d'attaquer à distance
                    if (u.getType() == TypeUnite.ELECTRIQUE) {
                        Distance pika = (Distance) u;

                        if (pika.getListeCasesTerrainAttaquables().size() != 0) {
                            pika.combatDistance(
                                    pika.getListeCasesTerrainAttaquables().get(0).getUnite());
                        }
                    }
						
                    // Sinon : 
                    // vérifier s'il y a un pokémon ou une potion dans le champ d'action
						
                    if (u.getDeplacement() > 0) {
                        Pokemon pokemon = null;
                        Potion potion = null;
							
                        // Récupération du dernier pokémon ennemi et de la dernière potion dans le 
                        // champ d'action. 
                        for (int i1 = 0; i1 < champAction.size(); i1++) {
                            CaseTerrain cT = champAction.get(i1);

                            if (cT.getUnite() != null) {
                                if (cT.getUnite().getType() == TypeUnite.POTION) {
                                    potion = (Potion) cT.getUnite();
                                } else {
                                    assert this.ennemi != null;
                                    if (cT.getUnite().estPokemon()
                                            && cT.getUnite().getEquipe()
                                                    == this.ennemi) {
                                        pokemon = (Pokemon) cT.getUnite();
                                    }
                                }
                            }
                        }
							
                        // S'il y a un pokémon ennemi dans le champ d'action :
                        // calculer l'itinéraire vers le pokémon
                        // le lancer (le combat sera lancé automatiquement)
                        if (pokemon != null) {
                            pokemon.getCaseTerrain().coutDAcces(u, it, null);
                            it.valider();
                        } else {
                            // Sinon, s'il y a une potion :
                            // aller vers la potion
                            if (potion != null) {
                                potion.getCaseTerrain().coutDAcces(u, it, null);
                                it.valider();
                            } else {
                                // S'il n'y a rien :
                                // Réduire l'itinéraire vers la cible (itinéraire déjà calculé
                                // précédemment) jusqu'à la case la plus loin du champ d'action
                                Itineraire itFinal = new Itineraire((Pokemon) u);

                                itFinal.reinitialiserCompletement();
                                ArrayList<CaseTerrain> listeCase = it.getListeCases();

                                itFinal.ajouterCaseSansDepl(
                                        listeCase.get(0).getCoordonnees());
                                int i1 = 1;

                                while (i1 < listeCase.size()
                                        && champAction.contains(
                                                listeCase.get(i1))) {
                                    itFinal.ajouterCase(
                                            listeCase.get(i1).getCoordonnees());
                                    i1++;
                                }
                            }
                        }
                    }
					
                }
            }
            if (this.getDresseur() == null
                    || this.getEnnemi().getDresseur() == null) {
                break;
            }
        }
        
        this.disp();
        
        this.getEnnemi().disp();
    }

    /**
     * Fixer la partie à laquelle participe l'équipe
     * @param p partie à laquelle participe l'équipe
     */
    public void setPartie(Partie p) {
        this.partie = p;
    }
	
    /**
     * Accesseur à la partie à laquelle participe l'équipe
     * @return la partie à laquelle participe l'équipe
     */
    public Partie getPartie() {
        return partie;
    }
	
    /**
     * Ajoute une unité à l'équipe
     * @param u l'unité à ajouter
     */
    public void ajouterUnite(Unite u) {
        unites.add(u);
        u.ajouterEquipe(this);
    }
	
    /**
     * Retire une unité à l'équipe
     * @param u l'unité à retirer
     */
    public void retirerUnite(Unite u) {
        unites.remove(u);
    }
	
    /**
     * Vérifie s'il y a encore des unités déplaçables dans l'équipe
     * @return true s'il y a au moins une unité déplaçable dans l'équipe
     */
    public boolean encoreDeplacer() {
        for (Unite u : unites) {
            if (u.getDeplacement() > 0) {
                return true;
            }
        }
        return false;
    }
	
    /**
     * Passer à l'unité suivante dans l'équipe (remplacer l'unité courante par sa suivante)
     * On considère que l'unité suivant la dernière est la première
     */
    public void passerSuivant() {
        int num = unites.lastIndexOf(uniteCourante);

        if (num == unites.size() - 1) {
            uniteCourante = unites.get(0);
        } else {
            uniteCourante = unites.get(num + 1);
        }
    }
	
    /**
     * Passer à l'unité suivante dans l'équipe qui a encore des points de déplacement
     * On considère que l'unité suivant la dernière est la première
     */
    public void passerSuivantDeplacement() {
        int numDepart = unites.lastIndexOf(uniteCourante);
        int num = numDepart + 1;

        while (num != numDepart && unites.get(num).getDeplacement() == 0) {
            passerSuivant();
            num++;
        }
        if (num != numDepart) {
            passerSuivant();
        }
    }
	
    /**
     * Tour de jeu d'une équipe 
     * @param action l'action à effectuer
     */
    public void jouerUnTour(TypeAction action) {
        if (action == TypeAction.ATTAQUER
                && uniteCourante.getType() == TypeUnite.ELECTRIQUE) {}
        if (action == TypeAction.DEPLACER
                && uniteCourante.getType() != TypeUnite.DRESSEUR
                && uniteCourante.getDeplacement() > 0) {}
    }

    public void setNom(String nom) {
        this.nom = nom;
    }

    public String getNom() {
        return nom;
    }

    public void setCouleur(Color couleur) {
        this.couleur = couleur;
    }

    public Color getCouleur() {
        return couleur;
    }

    public void setType(TypeEquipe type) {
        this.type = type;
    }

    public TypeEquipe getType() {
        return type;
    }

    public void setNbPotions(int nbPotions) {
        this.nbPotions = nbPotions;
    }

    public int getNbPotions() {
        return nbPotions;
    }

    /**
     * Fonction d'initialisation d'une équipe, avant de commencer la partie.
     * - initialise la chance de toutes les unités (déjà fait dans l'éditeur mais refait dans le
     * jeu afin de ne pas toujours avoir la même partie
     * - initialise la stratégie offensive ou défensive pour les unités si c'est une IA
     */
    public void init() {
        Iterator<Unite> i = unites.iterator();
		
        while (i.hasNext()) {
            i.next().initChance();
        }
		
        nbPotions = getPartie().getRegle().getNbPotion();
		
        if (type == TypeEquipe.ORDINATEUR) {
            for (Unite u : unites) {
                u.initStrategie();
            }
        }
    }
	
    public void init(Partie p) {}
	
    public Iterator<Unite> getIteratorUnites() {
        return unites.iterator();
    }
	
    public ArrayList<Unite> getUnites() {
        return unites;
    }
	
    public void disp() {
        
    }
	
    /**
     * Fonction qui permet d'obtenir la liste des cases visibles par l'équipe.
     * @return ArrayList de CaseTerrain.
     */
    public ArrayList<CaseTerrain> getCasesAffichables() {
        ArrayList<ArrayList<CaseTerrain>> toutes = new ArrayList<ArrayList<CaseTerrain>>();
		
        toutes.ensureCapacity(unites.size());
		
        for (int i = 0; i < unites.size(); i++) {
            ArrayList<CaseTerrain> casesUnite = new ArrayList<CaseTerrain>();

            casesUnite.add(unites.get(i).getCaseTerrain());
            toutes.add(
                    getCasesAffichables(casesUnite, unites.get(i).getVision()));
        }
		
        ArrayList<CaseTerrain> ret = new ArrayList<CaseTerrain>();
		
        for (ArrayList<CaseTerrain> l : toutes) {
            ret.addAll(l);
        }
		
        return ret;
		
        /* ArrayList<CaseTerrain> casesUnite = new ArrayList<CaseTerrain> ();
         for (int i = 0 ; i < unites.size() ; i++) {
         
         casesUnite.add(unites.get(i).getCaseTerrain());
         }
         
         return casesUnite;*/
    }
	
    /**
     * Fonction qui retourne la liste des caseTerrain visibles à partir des CaseTerrain passées en paramètre, et avec un champ de vision de taille champ_restant.
     * @param liste liste des cases à partir desquelles il faut calculer la liste des cases affichables
     * @param champRestant champ de vision à partir du 1er paramètre
     * @return liste des cases affichables
     */
    public ArrayList<CaseTerrain> getCasesAffichables(ArrayList<CaseTerrain> liste, int champRestant) {
        if (champRestant == 0) {
            return liste;
        }
		
        int sizeListInit = liste.size();

        for (int i = 0; i < sizeListInit; i++) {
            ArrayList<CaseTerrain> tmp = (ArrayList<CaseTerrain>) liste.get(i).getCaseAdjacente();

            tmp.removeAll(liste);
            liste.addAll(tmp);
        }
			
        return getCasesAffichables(liste, champRestant - 1);
    }

    /**
     * Accesseur sur une ArrayList contenant toutes les cases sur lesquelles il y a des Pokémons de l'équipe.
     * @return ArrayList
     */
    public ArrayList<CaseTerrain> getCasesUnites() {
        ArrayList<CaseTerrain> ret = new ArrayList<CaseTerrain>();

        for (Unite u : unites) {
            ret.add(u.getCaseTerrain());
        }
        return ret;
    }
	
    public void initTour() {
        Iterator<Unite> i = unites.iterator();
		
        while (i.hasNext()) {
            i.next().initTour();
        }
    }
	
    /**
     * Incrémente d'un le nombre de potion(s) utilisables par l'équipe.
     */
    public void gainPotion() {
        nbPotions++;
    }
	
    /**
     * Méthode à appeler en fin de tour, pour régénérer les PV en fonction des règles, pour les pokémons qui ne se sont pas déplacés
     */
    public void regenPVfinTour() {
        for (Unite u : unites) {
            u.regenPVfinTour();
        }
    }
}
